package cn.jiangjiesheng.bootstrap.commons.aspect;

import cn.jiangjiesheng.bootstrap.commons.annotation.LatencyLogging;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * Created by dev@jiangjiesheng.cn on 2022/11/24  <br/>
 *
 * @author dev@jiangjiesheng.cn
 */
@Aspect
@Component
@Slf4j
public class LatencyLoggingAspect {

    private static final String LATENCY_LOGGING_FORMAT = "[LatencyLogging] Method {} cost {} ms (from class {}).";

    @Around("@annotation(cn.jiangjiesheng.bootstrap.commons.annotation.LatencyLogging))")
    public Object dealProcess(ProceedingJoinPoint pjd) throws Exception {
        final StopWatch stopWatch = new StopWatch();
        final Class<?> targetClazz = pjd.getTarget().getClass();
        MethodSignature ms = (MethodSignature) pjd.getSignature();
        Method targetMethod = targetClazz.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        final LatencyLogging latencyLogging = targetMethod.getAnnotation(LatencyLogging.class);
        stopWatch.start();
        Object result = null;

        //执行目标方法
        try {
            result = pjd.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        stopWatch.stop();
        final long duration = stopWatch.getTime(TimeUnit.MILLISECONDS);
        final long thresholdInMs = latencyLogging.timeUnit().toMillis(latencyLogging.threshold());
        if (latencyLogging.always()) {
            printLogging(targetClazz, targetMethod, latencyLogging, duration);
        } else if (duration >= thresholdInMs) {
            printLogging(targetClazz, targetMethod, latencyLogging, duration);
        }
        return result;
    }

    private void printLogging(Class<?> targetClazz, Method targetMethod, LatencyLogging latencyLogging, long durationInMs) {
        switch (latencyLogging.logLevel()) {
            case DEBUG:
                log.debug(LATENCY_LOGGING_FORMAT, targetMethod.getName(), durationInMs, targetClazz.getCanonicalName());
                break;
            case INFO:
                log.info(LATENCY_LOGGING_FORMAT, targetMethod.getName(), durationInMs, targetClazz.getCanonicalName());
                break;
            case WARN:
                log.warn(LATENCY_LOGGING_FORMAT, targetMethod.getName(), durationInMs, targetClazz.getCanonicalName());
                break;
            case ERROR:
                log.error(LATENCY_LOGGING_FORMAT, targetMethod.getName(), durationInMs, targetClazz.getCanonicalName());
                break;
            case TRACE:
                log.trace(LATENCY_LOGGING_FORMAT, targetMethod.getName(), durationInMs, targetClazz.getCanonicalName());
                break;
            default:
                break;
        }
    }
}
